/** @file
 * Implements a simple register based virtual machine with a 2d array of
 * processes competeing for energy and space for digital life simulation.
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "cellvm.h"

static void dummycb(int x, int y, char didsomthing) {
}

int cluster_init(struct cell_cluster *cluster) {
	int x,y,acount;

	memset(cluster, '\0', sizeof *cluster);
	acount = 0;
	
	/* Malloc some space for the cells and clear their memory. */
	for(x = 0; x < X; x++) {
		for(y = 0; y < Y; y++) {
			if(!(cluster->cells[x][y] = malloc(sizeof(struct cell_proc))))
			   exit(1);
			   
			memset(cluster->cells[x][y], '\0', sizeof(struct cell_proc));
			acount += sizeof(struct cell_proc);
		}
	}
#ifdef DEBUG
	printf("Cell alloc: %db, %dx%dx%d\n", acount, X, Y, sizeof(struct cell_proc));
#endif
	add_callback(&cluster->callbacks, dummycb, 0xBABE, "VM_INTERNAL_DUMMY");
	return 0;
}

void cluster_free(struct cell_cluster *cluster) {
	int x,y,acount;
	acount = 0;
	
	for(x= 0; x < X; x++) {
		for(y = 0; y < Y; y++) {
			free(cluster->cells[x][y]);
			acount += sizeof(struct cell_proc);
		}
	}
#ifdef DEBUG
	printf("Cell free: %db\n", acount);
#endif
}

int cell_reap(const struct cell_cluster *cluster, int x, int y) {
	struct cell_proc *current;

	current = cluster->cells[x][y];

	/* Reap a dead cell if its energy has become 0 */
	if(current->gen > 0 && current->energy <= 0) {
#ifdef DEBUG
		printf("Reaper:%dx%d, gen:%d, tick:%d\n", x, y, current->gen, cluster->tick);
#endif
		memset(current, '\0', sizeof(struct cell_proc));
		return 0;
	}
	return 1;
}

int cluster_sched(struct cell_cluster *cluster) {
	struct cell_proc *current;
	int x, y, i;
	char didstuff;
	
	while(1) {
		didstuff = 0;

		/* Proc a random cell. */
		x = RANDX;
		y = RANDY;
		if(!(current = proc_cell(cluster, x, y, &didstuff))) {
#ifdef DEBUG
			printf("Cell table error\n");
#endif
			exit(1);
		}
			
		/* Do any reaping/callbacks that need doing then incremen the tick. */
		cell_reap(cluster, x, y); 
		do_callbacks(&cluster->callbacks, cluster->tick, x, y, didstuff);
		cluster->tick++;			
	}
	return 0;
}

struct cell_proc *proc_cell(const struct cell_cluster *cluster, int x, int y, char *didstuff) {
	int reg0, stop, instptr, direct, tmp;
	struct cell_proc *cell, *neighb;
	struct timeval pcounts, pcounte;
	
	reg0 = stop = instptr = 0;
	
	if((cell = cluster->cells[x][y]) == NULL)
		return NULL;
	
	/* If the instrucions arnt null and theres enough energy. */
	if(cell->instructions && cell->energy > 0) {
		*didstuff = 1;
#ifdef DEBUGHRT
		printf("Starting HRT for proc_cell\n");
		gettimeofday(&pcounts, NULL); /* chuck a performance counter here. */
#endif
#ifdef DEBUG
		printf("Tick %d, cell:%dx%d, energy:%d, gen:%d\n", cluster->tick, x, y, cell->energy, cell->gen);
#endif
		direct = rand() % 4;
		/* Process the cells instructions (if it has any) untill its energy has run out, it has no 
		 * instructions left or a STOP opcode is found. */
		while((cell->energy > 0) && (instptr < CSIZE) && !stop) {
#ifdef DEBUG
			printf("\tiptr:0x%x, inst:0x%x, reg0:0x%x, dir:0x%x, energy:%d\n", instptr, cell->instructions[instptr], reg0, direct, cell->energy);
#endif
			switch(cell->instructions[instptr]) {
				case NOOP: 
					break;
				case STOP: 
					stop = 1;
					break;
				case INCR: 
					reg0++;
					break;
				case DNCR:
					reg0--;
					break;
				case ZERO:
					reg0 = 0;
					break;
				case TURN: 
					direct = reg0;
					break;
				case CRCH: 
					break;
				case SPOR:
					/* Cant SPOR if you only have one energy, you will spawn a dead child, 
 * 					   hrm maybe we should?. */
					if(cell->energy <= 1)
						break;
 
					if(!(neighb = get_neighbour(cluster, x, y, direct))) {
						return NULL; /* invalod neighbour, should not happen. */
					}
					/* Spor ok if the cell gen is zero. */
					if(neighb->gen == 0) {
						tmp = neighb->energy;
						/* Copy cell's data to neighbou,increment its gen and take away an energy as a 
  						 * creation cost. */
						memcpy(neighb, cell, sizeof(struct cell_proc)); 
						neighb->gen++;
						neighb->energy--;
						 /* Give the child cell the energy found in cell pre spor. */
						neighb->energy += tmp;
#ifdef DEBUG
						printf("\tspor:true\n");
#endif
					}
					break;
				case RDIR:
					direct = rand() % 4;
					break;
				default: /* INVALID OPCODE. */
					break;
			}
			cell->energy--;
			instptr++;
		}
#ifdef DEBUGHRT
		gettimeofday(&pcounte, NULL);
		printf("HRT: proc_cell runtime: %d\n", (pcounte.tv_usec - pcounts.tv_usec));
#endif
#ifdef DEBUG
		printf("Cell stopped: iptr:0x%x/0x%x, inst:0x%x, stp:%d, energy:%d\n", instptr-1, CSIZE, cell->instructions[instptr-1], stop, cell->energy);
		if(ARTIFICIAL_LIMIT > 0)
			sleep(ARTIFICIAL_LIMIT); /* Sleep so we can see results. */
#endif
	}
	return cell;
}

int get_neighbour_coords(int x, int y, int direction, int *xp, int *yp) {
        /* Using torodial space, which means when a neighvour is requested on an edge
         * it will be wrapped to the other side of the table. */

	switch(direction) {
		case LEFT:
			if(x-1 >= 0) {
				*xp = x-1;
				*yp = y;
			}
			else {
				*xp = X-1;
				*yp = y;
			}
			break;
		case RIGHT:
			if(x+1 <= X-1) {
				*xp = x+1;
				*yp = y;
			}
			else {
				*xp = 0;
				*yp = y;
			}
			break;
		case UP:
			if(y-1 >= 0) {
				*xp = x;
				*yp = y-1;
			}
			else {
				*xp =  x;
				*yp = Y-1;
			}
			break;
		case DOWN:
			if(y+1 <= Y-1) {
				*xp = x;
				*yp = y+1;
			}
			else {
				*xp = x;
				*yp = 0;
			}
			break;
		default:
			return -1;
	}
	return 0;
}

struct cell_proc *get_neighbour(const struct cell_cluster *cluster, int x, int y, int direction) {
	int xp, yp;
	if(get_neighbour_coords(x, y, direction, &xp, &yp) != -1)
		return cluster->cells[xp][yp];
	else return NULL;
}

void cell_pop(const struct cell_cluster *cluster, int x, int y, int gen, int energy, const char instructions[CSIZE]) {
	cluster->cells[x][y]->gen = gen;
	cluster->cells[x][y]->energy = energy;
	if(instructions)
		memcpy(cluster->cells[x][y]->instructions, instructions, CSIZE);
}

